package org.vellos.halide.parse;

import java.util.ArrayList;

public class Parser {

  // TODO: Maybe make this do BNF like a sane parser would.  Just a thought.

  private static boolean isValidExpression(String expression) {
    boolean parensEncountered = false;
    int countUnclosedParens = 0;

    for(char c : expression.toCharArray()) {
      if(countUnclosedParens < 0) {
        // Parens are listed out of order, eg: )))(((, (()))
        return false;
      }

      if(c == '(') {
        countUnclosedParens++;
        parensEncountered = true;
      } else if(c == ')') {
        countUnclosedParens--;
      }
    }

    if(parensEncountered && countUnclosedParens == 0) {
      return true;
    } else if(!parensEncountered) {
      // TODO: Advise VM to interpret as a varible name, and respond with the value.
    }

    return false;
  }

  public static ExpressionTree parseExpressionToTree(String expression) throws InvalidExpressionException {
    if(!isValidExpression(expression)) {
      throw new InvalidExpressionException("Not a valid S-Expression or variable name.");
    }

    if(!expression.contains("(") && !expression.contains(")")) {
      // Short circuiting for simple statements that are not S-expressions.
      ArrayList<Object> lexicon = new ArrayList<Object>();
      lexicon.add(expression.trim());
      ExpressionNode root = new ExpressionNode(lexicon, null);
      return new ExpressionTree(root);
    }

    ExpressionNode root = parseExpression(expression);
    return new ExpressionTree(root);
  }

  public static ExpressionNode parseExpression(String expression) {
    ArrayList<String> lexicons = splitLexicons(expression);
    ArrayList<Object> nodeElements = new ArrayList<Object>();

    ExpressionNode root = new ExpressionNode();

    for (String lexicon : lexicons) {
      if (lexicon.contains("(") || lexicon.contains(")")) {
        nodeElements.add(parseExpression(lexicon, root));
      } else {
        nodeElements.add(lexicon);
      }
    }

    root.setLexicons(nodeElements);
    return root;
  }

  private static ExpressionNode parseExpression(String expression, ExpressionNode parent) {
    // Recursive descent portion of parseExpression().

    ArrayList<String> lexicons = splitLexicons(expression);
    ArrayList<Object> nodeElements = new ArrayList<Object>();

    ExpressionNode node = new ExpressionNode();

    for (String lexicon : lexicons) {
      if (lexicon.contains("(") || lexicon.contains(")")) {
        nodeElements.add(parseExpression(lexicon, node));
      } else {
        nodeElements.add(lexicon);
      }
    }

    node.setLexicons(nodeElements);
    node.setParent(parent);
    return node;
  }

  public static ArrayList<String> splitLexicons(String expression) {
    ArrayList<String> lexicons = new ArrayList<String>();

    int posFirstOpenParens = -1;
    int posLastCloseParens = -1;

    for (int i = 0, l = expression.length(); i < l; i++) {
      if (expression.charAt(i) == '(') {
        posFirstOpenParens = i;
        break;
      }
    }

    for (int i = expression.length() - 1; i > 0; i--) {
      if (expression.charAt(i) == ')') {
        posLastCloseParens = i;
        break;
      }
    }

    // Lops off current expression's outermost parens.
    expression = expression.substring(posFirstOpenParens + 1, posLastCloseParens);

    int posLexiconStart = 0;
    int openParensEncountered = 0;

    for (int i = 0, l = expression.length(); i < l; i++) {

      // Prevent dividing up nested S-expressions just yet.
      // Disable lexicon chopping until the expression has
      //   been worked over entirely.
      if (expression.charAt(i) == '(') {
        if (openParensEncountered == 0) {
          String lexicon = expression.substring(posLexiconStart, i);
          if (!lexicon.isEmpty()) {
            lexicons.add(lexicon);
          }
          posLexiconStart = i;
        }
        openParensEncountered++;
      } else if (expression.charAt(i) == ')') {
        openParensEncountered--;
      }

      if (expression.charAt(i) == ' ' && openParensEncountered == 0) {
        String lexicon = expression.substring(posLexiconStart, i);
        if (!lexicon.isEmpty()) {
          lexicons.add(lexicon);
        }
        posLexiconStart = i + 1;
      } else if (expression.charAt(i) == ')' && openParensEncountered == 0) {
        String lexicon = expression.substring(posLexiconStart, i + 1);
        if (!lexicon.isEmpty()) {
          lexicons.add(lexicon);
        }
        posLexiconStart = i + 1;
      } else if (i == l - 1) {
        // End of the string, finish the last lexicon.
        String lexicon = expression.substring(posLexiconStart);
        if (!lexicon.isEmpty()) {
          lexicons.add(lexicon);
        }
      }
    }

    return lexicons;
  }
}
